UNPKG

studiocms

Version:

Astro Native CMS for AstroDB. Built from the ground up by the Astro community.

100 lines (91 loc) 4.04 kB
import { type OAuthProviders, oAuthProviders } from 'studiocms:plugins/auth/providers'; import type { APIContext, APIRoute } from 'astro'; import { Effect, genLogger } from '../../../../../effect.js'; /** * Creates a standardized HTTP response for authentication provider errors. * * @param error - The error message to include in the response body. * @param status - The HTTP status code for the response. * @returns A `Response` object with a JSON body containing the error message and the specified status code. */ export const ProviderResponse = ( error: string, status: number ): Effect.Effect<Response, never, never> => Effect.succeed(new Response(JSON.stringify({ error }), { status })); /** * Executes the provided API route handler function with the given context and returns its resulting Promise. * * @param fn - The API route handler function to execute. * @param context - The context object to pass to the handler function. * @returns A Promise that resolves to a Response object returned by the handler. */ async function promisifyFn(fn: APIRoute, context: APIContext): Promise<Response> { return await fn(context); } /** * Wraps an API route handler function in an Effect, converting it into a promise-based effectful computation. * * @param fn - The API route handler function to be executed. * @param context - The API context to be passed to the handler. * @returns An Effect that resolves with the result of the handler or rejects with an error if execution fails. * * @remarks * If the handler throws an error, it is caught and wrapped in a new `Error` with a descriptive message. */ const promisify = Effect.fn(function* (fn: APIRoute, context: APIContext) { return yield* Effect.tryPromise({ try: () => promisifyFn(fn, context), catch: (error) => new Error(`Failed to execute API route: ${error}`), }); }); /** * Dispatches an authentication request to the appropriate OAuth provider handler. * * This generator function locates the specified provider by its `safeName` from the provided list, * checks if the provider is enabled, and then invokes the appropriate handler (`initSession` or `initCallback`) * for the authentication flow. If the provider or handler is not found, or if the provider is not enabled, * it yields an appropriate error response. * * @param context - The API context containing request parameters and other relevant data. * @param handler - The handler to invoke, either `'initSession'` or `'initCallback'`. * @param providers - An array of provider configurations, each containing a `safeName`, `enabled` flag, * and optional `initSession` and `initCallback` route handlers. * @returns A generator yielding the result of the provider handler or an error response. */ const dispatchToAuthProvider = ( context: APIContext, handler: 'initSession' | 'initCallback', providers: OAuthProviders ) => genLogger('OAuthAPIEffect.dispatchToAuthProvider')(function* () { const provider = context.params.provider; const matchedProvider = providers.find((p) => p.safeName === provider); if (!matchedProvider) { return yield* ProviderResponse('Provider not found', 404); } if (!matchedProvider.enabled) { return yield* ProviderResponse('Provider is not configured', 403); } const handlerFn = matchedProvider[handler]; if (!handlerFn) { return yield* ProviderResponse('Provider handler not found', 501); } return yield* promisify(handlerFn, context); }); export class OAuthAPIEffect extends Effect.Service<OAuthAPIEffect>()('OAuthAPIEffect', { effect: genLogger('studiocms/routes/api/auth/[provider]/_shared')(function* () { return { initSession: (context: APIContext) => dispatchToAuthProvider(context, 'initSession', oAuthProviders), initCallback: (context: APIContext) => dispatchToAuthProvider(context, 'initCallback', oAuthProviders), }; }), }) { // Export Dependency Providers /** * Main Dependencies Provider */ static Provide = Effect.provide(OAuthAPIEffect.Default); }